﻿// // --------------------------------------------------------------------------------------------------------------------
// // <copyright file="RemoteWebDriverEx.cs" company="Collaboris">
// //   Copyright Collaboris
// // </copyright>
// // <summary>
// //   
// // </summary>
// // --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.ShareTest.Core
{
    #region

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Drawing.Imaging;
    using System.Threading;

    using OpenQA.Selenium;
    using OpenQA.Selenium.IE;
    using OpenQA.Selenium.Interactions;
    using OpenQA.Selenium.Internal;
    using OpenQA.Selenium.Remote;
    using OpenQA.Selenium.Support.UI;

    using Selenium;

    #endregion

    /// <summary>
    /// 	The i web Driver extensions.
    /// </summary>
    public static class RemoteWebDriverEx
    {
        #region Public Methods

        /// <summary>
        /// 	The click with wait.
        /// </summary>
        /// <param name = "driver">
        /// 	The driver.
        /// </param>
        /// <param name = "locator">
        /// 	The by locator.
        /// </param>
        public static void ClickWithWait(this RemoteWebDriver driver, By locator)
        {
            IWebElement element = WaitForElementNotPresent(driver, locator);
            if (element != null)
            {
                element.Click();
            }
        }

        /// <summary>
        /// 	Overloads the FindElement function to include support for the JQuery selector class
        /// </summary>
        /// <param name = "driver">
        /// 	The driver.
        /// </param>
        /// <param name = "by">
        /// 	The by.
        /// </param>
        /// <returns>
        /// 	The find element.
        /// </returns>
        public static RemoteWebDriver FindElement(this RemoteWebDriver driver, JBy.JQueryBy by)
        {
            // First make sure we can use JQuery functions
            driver.LoadJQuery();

            // Execute the JQuery selector as a script
            RemoteWebDriver element =
                driver.ExecuteScript("return JQuery" + @by.Selector + ".get(0)") as RemoteWebDriver;

            if (element != null)
            {
                return element;
            }

            throw new NoSuchElementException("No element found with JQuery command: JQuery" + @by.Selector);
        }

        /// <summary>
        /// 	Overloads the FindElements function to include support for the JQuery selector class
        /// </summary>
        /// <param name = "driver">
        /// 	The driver.
        /// </param>
        /// <param name = "by">
        /// 	The by.
        /// </param>
        /// <returns>
        /// 	The find elements.
        /// </returns>
        public static ReadOnlyCollection<RemoteWebDriver> FindElements(this RemoteWebDriver driver, JBy.JQueryBy by)
        {
            // First make sure we can use JQuery functions
            driver.LoadJQuery();

            // Execute the JQuery selector as a script
            ReadOnlyCollection<RemoteWebDriver> collection =
                driver.ExecuteScript("return JQuery" + @by.Selector + ".get()") as ReadOnlyCollection<RemoteWebDriver>;

            // Unlike FindElement, FindElements does not throw an exception if no elements are found
            // and instead returns an empty list
            if (collection == null)
            {
                collection = new ReadOnlyCollection<RemoteWebDriver>(new List<RemoteWebDriver>()); // empty list
            }

            return collection;
        }

        /// <summary>
        /// 	The focus.
        /// </summary>
        /// <param name = "driver">
        /// 	The driver.
        /// </param>
        /// <param name = "element">
        /// 	The element.
        /// </param>
        public static void Focus(this RemoteWebDriver driver, IWebElement element)
        {
            for (int i = 0; i < RunSettings.OperationRetryTimes; i++)
            {
                try
                {
                    Actions builder = new Actions(driver);
                    builder.MoveToElement(element).Build().Perform();
                    return;
                }
                catch (Exception)
                {
                    Thread.Sleep(RunSettings.OperationRetryInterval);
                    Debug.Print("Failed to focus on element.");
                }
            }
        }

        /// <summary>
        /// 	The focus.
        /// </summary>
        /// <param name = "driver">
        /// 	The driver.
        /// </param>
        /// <param name = "locator">
        /// 	The locator.
        /// </param>
        public static void Focus(this RemoteWebDriver driver, string locator)
        {
            //TODO: is the url ok ?
            ISelenium selenium = new WebDriverBackedSelenium(driver, driver.Url);
            selenium.MouseOver(locator);
        }

        /// <summary>
        /// 	The focus on an element then click it.
        /// </summary>
        /// <param name = "webDriver">
        /// 	The web driver.
        /// </param>
        /// <param name = "id">
        /// 	The id.
        /// </param>
        public static void FocusAndClick(this RemoteWebDriver webDriver, By id)
        {
            webDriver.WaitForElementNotPresent(id);
            webDriver.Focus(webDriver.FindElement(id));
            webDriver.ClickWithWait(id);
        }

        /// <summary>
        /// 	The focus and click.
        /// </summary>
        /// <param name = "driver">
        /// 	The driver.
        /// </param>
        /// <param name = "element">
        /// 	The element.
        /// </param>
        public static void FocusAndClick(this RemoteWebDriver driver, IWebElement element)
        {
            Focus(driver, element);

            element.Click();
        }

        public static T GetAttributeAsType<T>(this IWebElement element, string attributeName)
        {
            string value = element.GetAttribute(attributeName) ?? string.Empty;
            return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(value);
        }

        public static string GetText(this IWebDriver driver)
        {
            return driver.FindElement(By.TagName("body")).Text;
        }

        /// <summary>
        /// 	Safely checks if a given elementis found.
        /// </summary>
        /// <param name = "webDriver"></param>
        /// <param name = "by"></param>
        /// <returns></returns>
        public static bool IsElementPresent(this RemoteWebDriver webDriver, By by)
        {
            webDriver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromMilliseconds(500)); 

            ReadOnlyCollection<IWebElement> elements = webDriver.FindElements(@by);

            if (RunSettings.DriverTimeout != null)
            {
                webDriver.Manage().Timeouts().ImplicitlyWait(RunSettings.DriverTimeout.Value);
            }

            if (elements == null || elements.Count == 0)
            {
                return false;
            }

            return true;
        }

        public static bool IsElementPresent(this IWebElement element, RemoteWebDriver webDriver, By by)
        {
            webDriver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromMilliseconds(500));

            ReadOnlyCollection<IWebElement> elements = element.FindElements(@by);

            if (RunSettings.DriverTimeout != null)
            {
                webDriver.Manage().Timeouts().ImplicitlyWait(RunSettings.DriverTimeout.Value);
            }

            if (elements == null || elements.Count == 0)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// 	Return whether JQuery is loaded in the current page
        /// </summary>
        /// <param name = "driver">
        /// 	The driver.
        /// </param>
        /// <returns>
        /// 	The j query loaded.
        /// </returns>
        public static bool IsJQueryLoaded(this RemoteWebDriver driver)
        {
            bool result = false;
            try
            {
                result = (bool)driver.ExecuteScript("return typeof JQuery == 'function'");
            }
            catch (WebDriverException)
            {
            }

            return result;
        }

        /// <summary>
        /// 	Load JQuery from an external URL to the current page
        /// </summary>
        /// <param name = "driver">
        /// 	The driver.
        /// </param>
        /// <param name = "version">
        /// 	The version.
        /// </param>
        /// <param name = "timeout">
        /// 	The timeout.
        /// </param>
        public static void LoadJQuery(this RemoteWebDriver driver, string version = "any", TimeSpan? timeout = null)
        {
            if (!IsJQueryLoaded(driver))
            {
                // Get the url to load JQuery from
                string jqueryUrl;
                if (version == String.Empty || version.ToLower() == "latest")
                {
                    jqueryUrl = "http://code.jquery.com/jquery-latest.min.js";
                }
                else
                {
                    jqueryUrl = "https://ajax.googleapis.com/ajax/libs/jquery/" + version + "/jquery.min.js";
                }

                // Script to load JQuery from external site
                string versionEnforceScript = version.ToLower() != "any"
                                                  ? String.Format(
                                                      "if (typeof JQuery == 'function' && JQuery.fn.jquery != '{0}') JQuery.noConflict(true);",
                                                      version)
                                                  : String.Empty;
                string loadingScript =
                    @"if (typeof JQuery != 'function')
                  {
                      var headID = document.getElementsByTagName('head')[0];
                      var newScript = document.createElement('script');
                      newScript.type = 'text/javascript';
                      newScript.src = '"
                    + jqueryUrl
                    +
                    @"';
                      headID.appendChild(newScript);
                  }
                  return (typeof JQuery == 'function');";

                bool loaded = (bool)driver.ExecuteScript(versionEnforceScript + loadingScript);

                if (!loaded)
                {
                    // Wait for the script to load
                    // Verify library loaded
                    if (!timeout.HasValue)
                    {
                        timeout = new TimeSpan(0, 0, 30);
                    }

                    int timePassed = 0;
                    while (!driver.IsJQueryLoaded())
                    {
                        Thread.Sleep(500);
                        timePassed += 500;

                        if (timePassed > timeout.Value.TotalMilliseconds)
                        {
                            throw new Exception("Could not load JQuery");
                        }
                    }
                }

                string v = driver.ExecuteScript("return JQuery.fn.jquery").ToString();
            }
        }

        /// <summary>
        /// 	For IE only this will save a screenshot.
        /// </summary>
        /// <param name = "driver">
        /// </param>
        /// <param name = "filePath">
        /// </param>
        public static void SaveIeScreenShot(this RemoteWebDriver driver, string filePath)
        {
            InternetExplorerDriver internetExplorerDriver = driver as InternetExplorerDriver;
            if (internetExplorerDriver != null)
            {
                Screenshot screenShot = internetExplorerDriver.GetScreenshot();
                screenShot.SaveAsFile(filePath, ImageFormat.Png);
            }
        }

        /// <summary>
        /// Only sends Keys 
        /// </summary>
        /// <param name="driver"></param>
        /// <param name="element"></param>
        /// <param name="keysToSend"></param>
        public static void SendKeysAndClear(this RemoteWebDriver driver, IWebElement element, string keysToSend)
        {
            if (string.IsNullOrEmpty(keysToSend))
            {
                keysToSend = string.Empty;
            }

            element.Clear();
            element.SendKeys(keysToSend);
            
        }

        public static void SendKeysAndClearIfDiff(this RemoteWebDriver driver, IWebElement element, string keysToSend)
        {
            if (element.GetAttribute("value") != keysToSend)
            {
                SendKeysAndClear(driver, element, keysToSend);
            }
        }

        public static void SetAttribute(this IWebElement element, string attributeName, string value)
        {
            IWrapsDriver wrappedElement = element as IWrapsDriver;
            if (wrappedElement == null)
            {
                throw new ArgumentException("element", "Element must wrap a web driver");
            }

            IWebDriver driver = wrappedElement.WrappedDriver;
            IJavaScriptExecutor javascript = driver as IJavaScriptExecutor;
            if (javascript == null)
            {
                throw new ArgumentException(
                    "element", "Element must wrap a web driver that supports javascript execution");
            }

            javascript.ExecuteScript(
                "arguments[0].setAttribute(arguments[1], arguments[2])", element, attributeName, value);
        }

        /// <summary>
        /// 	The wait for element not present.
        /// </summary>
        /// <param name = "driver">
        /// 	The Driver.
        /// </param>
        /// <param name = "bylocator">
        /// 	The bylocator.
        /// </param>
        /// <returns>
        /// 	The wait for element not present.
        /// </returns>
        public static IWebElement WaitForElementNotPresent(this RemoteWebDriver driver, By bylocator)
        {
            WebDriverWait wait = new WebDriverWait(driver, RunSettings.DriverTimeout.Value);
            wait.Until(x => driver.FindElement(bylocator));

            return driver.FindElement(bylocator);

            //////for (int i = 0; i < RunSettings.OperationRetryTimes; i++)
            //////{
            //////    try
            //////    {
            //////        IWebElement element = driver.FindElement(bylocator);

            //////        if (element != null)
            //////        {
            //////            return element;
            //////        }
            //////    }
            //////    catch (Exception exception)
            //////    {
            //////        Thread.Sleep(RunSettings.OperationRetryInterval);

            //////        // Debug.Print(exception.ToString());
            //////    }
            //////}

            ////// throw new ElementNotVisibleException("Can't find an element, even after retries!");
        }

        public static void WaitForPageToLoad(this IWebDriver driver)
        {
            TimeSpan timeout = new TimeSpan(0, 0, 30);
            WebDriverWait wait = new WebDriverWait(driver, timeout);

            IJavaScriptExecutor javascript = driver as IJavaScriptExecutor;
            if (javascript == null)
            {
                throw new ArgumentException("driver", "Driver must support javascript execution");
            }

            wait.Until(
                (d) =>
                    {
                        try
                        {
                            string readyState =
                                javascript.ExecuteScript("if (document.readyState) return document.readyState;").
                                    ToString();
                            return readyState.ToLower() == "complete";
                        }
                        catch (InvalidOperationException e)
                        {
                            //Window is no longer available
                            return e.Message.ToLower().Contains("unable to get browser");
                        }
                        catch (WebDriverException e)
                        {
                            //Browser is no longer available
                            return e.Message.ToLower().Contains("unable to connect");
                        }
                        catch (Exception)
                        {
                            return false;
                        }
                    });
        }

        #endregion
    }
}